package com.ruoyi.controller;


import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.config.WxPayConfig;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.Verifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.cert.CertificatesManager;
import com.wechat.pay.contrib.apache.httpclient.exception.HttpCodeException;
import com.wechat.pay.contrib.apache.httpclient.exception.NotFoundException;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import okhttp3.HttpUrl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHeaders;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 微信支付控制器
 */
@RestController
@RequestMapping("weixinpay")
public class WxPayController {
    private static final Log log = LogFactory.getLog(WxPayController.class);

    @Autowired
    private WxPayConfig wxPayConfig;


    private static final String REQUEST_METHOD = "POST";

    private final ReentrantLock lock = new ReentrantLock();

    private static String NOTIFY_URL = "https://a.nps.tx06.com/weixinpay/native/callback";  //回调地址  暂时内网穿透

    private static String CURRENCY = "CNY";   //微信支付单位 人民币

    private static String CODE_URL = "code_url";    //微信native返回参数

    private static String REQUEST_URL = "https://api.mch.weixin.qq.com/v3/pay/transactions/native";   //native获取微信支付二维码连接

    private static final Integer RESPONSE_CODE = 200;

    /**
     * 发起微信支付请求
     */
    @PostMapping("/createNative")
    public Object createNative(Float money) throws Exception {
        log.info("发起微信支付请求APIv3版,支付金额:" + money);

        if (ObjectUtil.isNull(money)) {
            throw new IllegalAccessException();
        }
        String orderNo = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
        String jsonParams = buildRequestParams(money, orderNo);
        JSONObject jsonObject = sendRequest(jsonParams);
        String codeUrl = String.valueOf(jsonObject.get(CODE_URL));
        Map<String, Object> responseMap = new HashMap<>();
        responseMap.put("codeUrl", codeUrl);
        responseMap.put("orderNo", orderNo);
        return responseMap;
    }

    /**
     * 请求微信接口
     */
    private JSONObject sendRequest(String jsonParams) throws Exception {
        //http请求头设置的Authorization  当前时间戳
        long timestmap = System.currentTimeMillis() / 1000;
        //获取httpclient客户端 发送post请求
        HttpPost httpPost = new HttpPost(REQUEST_URL);
        String rendom = UUID.randomUUID().toString().replaceAll("-", "").substring(0, 20);
        HttpUrl url = HttpUrl.parse(REQUEST_URL);
        String token = getToken(url, timestmap, rendom, jsonParams);
        String lastToken = "WECHATPAY2-SHA256-RSA2048" + " " + token;
        log.info("请求Token 为: " + token);
        StringEntity se = new StringEntity(jsonParams, "utf-8");
        log.info("执行微信支付请求发送，参数为 : " + se.toString());
        // 请求体body 请求头的token accept和content_type
        httpPost.setEntity(se);
        httpPost.setHeader("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
        //httpPost.setHeader(HttpHeaders.AUTHORIZATION, lastToken);
        httpPost.setHeader(HttpHeaders.ACCEPT, "application/json");
        httpPost.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");

        CloseableHttpClient httpClient = wxPayConfig.getWxPayClient();
        CloseableHttpResponse httpResponse = httpClient.execute(httpPost);

        Integer statusCode = httpResponse.getStatusLine().getStatusCode();
        HttpEntity entity = httpResponse.getEntity();
        String res = EntityUtils.toString(entity);

        if (!statusCode.equals(RESPONSE_CODE) || res.isEmpty()) {
            log.error("微信支付统一下单错误 ===> " + statusCode);
            throw new RuntimeException("微信支付统一下单错误");
        }
        JSONObject jsonObject = JSON.parseObject(res);
        return jsonObject;
    }

    //构建请求参数
    private String buildRequestParams(Float money, String orderNo) {

        //只支持rmb  不用兑换成美金
        BigDecimal rmbAmount = new BigDecimal(money.toString());
        log.info("本次订单号为 :" + orderNo);
        String subject = "在线充值" + money + "元";

        //组参 wxPayConfig.getAppid()  wxPayConfig.getMchId() description  out_trade_no notify_url amount{ total currency}
        Map<String, Object> paramMap = new HashMap<>();
        paramMap.put("appid", wxPayConfig.getAppid());
        paramMap.put("mchid", wxPayConfig.getMchId());
        //商品描述
        paramMap.put("description", subject);
        //商户订单号
        paramMap.put("out_trade_no", orderNo);
        //回调地址 必须是https
        paramMap.put("notify_url", NOTIFY_URL);

        float totalFee = (money * 100);
        Map<String, Object> amountMap = new HashMap<>();
        amountMap.put("total", Integer.valueOf((int) totalFee));
        amountMap.put("currency", CURRENCY);
        //String jsonString = JSON.toJSONString(amountMap);
        paramMap.put("amount", amountMap);
        String jsonParams = JSON.toJSONString(paramMap);
        return jsonParams;
    }


    //需要签名的字符串
    private String getSignature(HttpUrl url, long timestamp, String random, String body) {
        String canonicalUrl = url.encodedPath();
        return REQUEST_METHOD + "\n"
                + canonicalUrl + "\n"
                + timestamp + "\n"
                + random + "\n"
                + body + "\n";
    }


    //根据密钥拿到签名串
    private String sign(byte[] message) {
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(wxPayConfig.getPrivateKey(wxPayConfig.getPrivateKeyPath()));
            sign.update(message);
            return Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return null;
    }

    //请求头里的Authorization  
    private String getToken(HttpUrl url, long timestamp, String random, String body) {
        try {
            String message = getSignature(url, timestamp, random, body);
            String signature = sign(message.getBytes("utf-8"));
            return "mchid=\"" + wxPayConfig.getMchId() + "\","
                    + "nonce_str=\"" + random + "\","
                    + "timestamp=\"" + timestamp + "\","
                    + "serial_no=\"" + wxPayConfig.getMchSerialNo() + "\","
                    + "signature=\"" + signature + "\"";
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 支付回调
     */
    @PostMapping("/native/callback")
    public Object callback(HttpServletRequest request, HttpServletResponse response) throws Exception {
        Map requestMap = getRequestMap(request);
        //开始按照验签进行拼接
        String id = String.valueOf(requestMap.get("id"));
        String resource = String.valueOf(requestMap.get("resource"));
        Map requestMap2 = (Map) JSON.parse(resource);
        log.info("微信支付解密出得到 id 为:" + id + "requestMap2 为:" + requestMap2.toString());


        String bodyAssociatedData = String.valueOf(requestMap2.get("associated_data"));
        String bodyNonce = String.valueOf(requestMap2.get("nonce"));
        String bodyCiphertext = String.valueOf(requestMap2.get("ciphertext"));
        log.info("微信支付解密出得到 bodyAssociated_data 为:" + bodyAssociatedData + " bodyNonce 为:"
                + bodyNonce + " bodyCiphertext 为:" + bodyCiphertext);

        //开始解密参数
        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String aes = aesUtil.decryptToString(bodyAssociatedData.getBytes(StandardCharsets.UTF_8),
                bodyNonce.getBytes(StandardCharsets.UTF_8), bodyCiphertext);

        Map decryptMap = (Map) JSON.parse(aes);
        //获取商户订单号
        String orderNo = String.valueOf(decryptMap.get("out_trade_no"));
        log.info("回调解密得到orderNo 为:" + orderNo);
        log.info("解密resources 得到的回调参数 aes为: " + aes);
        if (lock.tryLock()) {
            try {
                //这里处理你的订单逻辑  一定要加锁！！！！
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }
        return "";
    }

    private Map getRequestMap(HttpServletRequest request) throws IOException {
        String serial = request.getHeader("Wechatpay-Serial");
        log.info("回调得到证书序列号为 : " + serial);
        //先验签是否来自微信  时间戳 应答随机串
        String timestamp = request.getHeader("Wechatpay-Timestamp");
        String nonce = request.getHeader("Wechatpay-Nonce");
        String signature = request.getHeader("Wechatpay-Signature");
        log.info("微信支付回调请求头读取，timestamp为: " + timestamp + " nonce为: " + nonce + " signature为: " + signature);
        //读取请求体数据
        ServletInputStream inputStream = request.getInputStream();
        StringBuffer stringBuffer = new StringBuffer();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        String s;
        //读取回调请求体
        while ((s = bufferedReader.readLine()) != null) {
            stringBuffer.append(s);
        }
        String responseBody = stringBuffer.toString();
        log.info("微信支付回调请求体读取，结果为: " + responseBody);


        X509Certificate verifier = getVerifier();
        //拿到平台证书
        String realSerial = verifier.getSerialNumber().toString(16).toUpperCase();
        log.info("实时请求拿到的公众平台序列号realSerial为: " + realSerial);
        if (!realSerial.equals(serial)) {
            Assert.isFalse(true, "平台序列号不一致");
        }
        //使用官方验签工具进行验签
        boolean weChatPayReplySign = getWeChatPayReplySign(verifier, timestamp, nonce, responseBody, signature);
        if (!weChatPayReplySign) {
            log.error("微信支付验签失败");
            Assert.isFalse(true, "验签失败");
        }


        Map requestMap = (Map) JSON.parse(responseBody);
        return requestMap;
    }

    private boolean getWeChatPayReplySign(X509Certificate validCertificate, String weChatPayTimestamp,
                                          String weChatPayNonce, String body, String weChatPaySignature) {
        try {
            String str = Stream.of(weChatPayTimestamp, weChatPayNonce, body)
                    .collect(Collectors.joining("\n", "", "\n"));
            log.info("验签流程 得到str为 " + str);
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initVerify(validCertificate.getPublicKey());
            sign.update(str.getBytes(StandardCharsets.UTF_8));
            // 解码后的字符串
            byte[] decode = Base64.getDecoder().decode(weChatPaySignature.getBytes(StandardCharsets.UTF_8));
            return sign.verify(decode);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (SignatureException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        }
        return false;
    }


    private X509Certificate getVerifier() {
        X509Certificate validCertificate = null;
        try {
            PrivateKey merchantPrivateKey = wxPayConfig.getPrivateKey(wxPayConfig.getPrivateKeyPath());
            // 定时更新平台证书功能
            CertificatesManager certificatesManager = CertificatesManager.getInstance();
            // 向证书管理器增加需要自动更新平台证书的商户信息
            certificatesManager.putMerchant(wxPayConfig.getMchId(),
                    new WechatPay2Credentials(wxPayConfig.getMchId(),
                            new PrivateKeySigner(wxPayConfig.getMchSerialNo(), merchantPrivateKey)),
                    wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
            // 从证书管理器中获取verifier
            Verifier verifier = certificatesManager.getVerifier(wxPayConfig.getMchId());
            //拿到平台证书
            validCertificate = verifier.getValidCertificate();
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (NotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (HttpCodeException e) {
            e.printStackTrace();
        }
        return validCertificate;
    }


}